package com.supplychain.financial.parent.common.util;
/*
 * 商户测试环境代付请求,代付结果查询测试程序
 */

import com.supplychain.financial.parent.common.exception.CustomException;
import com.supplychain.financial.parent.common.model.MinShengClearingResponseDTO;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;

public class WeiZhi_DaiFu {
    private static SecretKeySpec secretKey;
    private static byte[] bsKey;
    private static final Logger logger = LoggerFactory.getLogger(WeiZhi_DaiFu.class);
    // ** 参数配置, 请使用贵阳民生提供的测试参数进行替换
    private static  String locTruststore = null;// "/Users/zhengchuan/Documents/MingShengBankKey/keys2/serverTrust_pwd_123456.keystore";
    private static  String pwdTruststore = null;//"123456";
    private static  String locKeystore = null;//"/Users/zhengchuan/Documents/MingShengBankKey/keys/456_client.keystore";
    private static  String pwdKeystore = null;//"123456";
    private static  String pwd = null;//"123456";
    private static  String aesKey = null;//"26xw702e977w3oxc5hf5j8cm";
    private static  String rmtUrl = null;//"https://120.77.77.20:9000/WebRoot/UserAuth";
    private static  String locPrk = null;//"/Users/zhengchuan/Documents/MingShengBankKey/keys2/client.der";
    private static  String locPuk = null;//"/Users/zhengchuan/Documents/MingShengBankKey/keys2/gycmbc_server_puk_nopwd.der";
    public static String merIds = null;
    static {
        FileInputStream fis = null;// 属性文件输入流
        try {
            fis = new FileInputStream(WeiZhi_DaiFu.class.getResource("/").getFile() + "shcs.properties");
            //System.out.println("文件路径：" + shcs_DaiFu.class.getResource("/").getFile());
            // 将属性文件流装载到Properties对象中
            Properties prop = new Properties();
            prop.load(fis);
            fis.close();// 关闭流
            locTruststore = prop.getProperty("locTruststoreWeiZhi");
            pwdTruststore = prop.getProperty("pwdTruststoreWeiZhi");
            locKeystore = prop.getProperty("locKeystoreWeiZhi");
            pwdKeystore = prop.getProperty("pwdKeystoreWeiZhi");
            pwd = prop.getProperty("pwdWeiZhi");
            aesKey = prop.getProperty("aesKeyWeiZhi");
            rmtUrl = prop.getProperty("rmtUrlWeiZhi");
            locPrk = prop.getProperty("locPrkWeiZhi");
            locPuk = prop.getProperty("locPukWeiZhi");
            merIds = prop.getProperty("merIdsWeiZhi");
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("加载配置文件shcs.propertes失败");
        }
    }

    /*
     * 设置AES秘钥
     */
    public static void setKey(String myKey) {
        MessageDigest sha = null;
        try {
            bsKey = myKey.getBytes("UTF-8");
            sha = MessageDigest.getInstance("SHA-256");
            bsKey = sha.digest(bsKey);
            bsKey = Arrays.copyOf(bsKey, 16);
            secretKey = new SecretKeySpec(bsKey, "AES");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /*
     *加载RSA私钥
     *@filename: 密钥钥存储路径，绝对路径
     *@return: 私钥
     */
    public static PrivateKey getPrk(String filename) throws Exception {
        byte[] keyBytes = Files.readAllBytes(new File(filename).toPath());
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    /*
     *加载RSA公钥
     *@filename: 密钥钥存储路径，绝对路径
     *@return: 公钥
     */
    public static PublicKey getPuk(String filename) throws Exception {
        byte[] keyBytes = Files.readAllBytes(new File(filename).toPath());
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }


    /*
     * 发送POST请求
     * @msg: 消息体
     * @locKeystore: SSL双向认证，客户端证书存储路径，绝对路径
     * @pwdKeystore: 读取keystore需要的密码
     * @locTruststore: SSL双向认证，用于验证远端服务器身份的证书存放路径，绝对路径
     * @pwdTruststore: 读取truststore需要的密码
     * @pwd: keystore中存储的私钥的保护密码
     * @rmtUrl: 目的url
     * @return: 响应报文密文
     */
    public static String sendingRequest(String msg, String locKeystore, String pwdKeystore, String locTruststore, String pwdTruststore, String pwd, String rmtUrl) throws IOException {
        KeyStore trustS = null;
        KeyStore keyS = null;
        SSLContext sslcontext = null;
        SSLConnectionSocketFactory sslsf = null;
        try {
            trustS = KeyStore.getInstance(KeyStore.getDefaultType());
            FileInputStream trustStream = new FileInputStream(new File(locTruststore));
            trustS.load(trustStream, pwdTruststore.toCharArray());

            //keyS = KeyStore.getInstance(KeyStore.getDefaultType());
            keyS = KeyStore.getInstance("PKCS12");
            FileInputStream keyStream = new FileInputStream(new File(locKeystore));
            keyS.load(keyStream, pwdKeystore.toCharArray());

            sslcontext = SSLContexts.custom()
                    .loadTrustMaterial(trustS, null)
                    .loadKeyMaterial(keyS, pwd.toCharArray())
                    .build();

            sslsf = new SSLConnectionSocketFactory(sslcontext, null, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        } catch (Exception e) {
            e.printStackTrace();
        }


        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        String resString = null;
        try {

            HttpPost httppost = new HttpPost(rmtUrl);
            CloseableHttpResponse response = null;
            try {
                HttpEntity entity = null;
                StringEntity se = new StringEntity(msg);
                httppost.setEntity(se);
                response = httpclient.execute(httppost);
                entity = response.getEntity();
                resString = EntityUtils.toString(entity, "utf-8");
                EntityUtils.consume(entity);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
        return resString;
    }

    /*
     * 计算签名
     * @prk: 签名私钥
     * @plaintext: 待签名串
     * @return: 签名
     */
    public static String sign(PrivateKey prk, String plaintext) {
        String sig = null;
        try {
            Signature ins = Signature.getInstance("SHA256withRSA");
            ins.initSign(prk);
            ins.update(plaintext.getBytes("UTF-8"));
            sig = new String(Base64.encodeBase64(ins.sign()), "utf-8");//Base64.getEncoder().encodeToString(ins.sign());

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sig;
    }


    /*
     * 验签
     * @locPuk: 验签所用公钥存放路径，绝对路径
     * @msg: 待验签串
     * @sig: 签名
     * return: True 如果验签通过, False 如果签约失败
     */
    public static boolean verify(PublicKey locPuk, String msg, String sig) {
        boolean sigVerified = false;
        try {
            Signature ins = Signature.getInstance("SHA256withRSA");
            ins.initVerify(locPuk);
            ins.update(msg.getBytes("UTF-8"));
            sigVerified = ins.verify(Base64.decodeBase64(sig.getBytes("utf-8")));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sigVerified;
    }

    /*
     * 对称密码算法加密
     * @strToEncrypt: 待加密串(明文）
     * @secret: 密码
     * @return: 加密后字符串(密文)
     */
    public static String encrypt(String strToEncrypt, String secret) {
        try {
            setKey(secret);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
            MessageDigest sha = null;
            sha = MessageDigest.getInstance("SHA-256");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Arrays.copyOf(sha.digest(secret.getBytes("UTF-8")), 16), "AES"));
            return new String(Base64.encodeBase64(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))), "utf-8");
        } catch (Exception e) {
            logger.error("Error while encrypting: " + e.toString());
        }
        return null;
    }

    public static String decrypt(String strToDecrypt, String secret) {
        try {
            setKey(secret);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            return new String(cipher.doFinal(Base64.decodeBase64(strToDecrypt.getBytes("utf-8"))));
        } catch (Exception e) {
            logger.error("解密失败! " + e.toString());
            e.printStackTrace();
        }
        return null;
    }

    /*
     * 发送请求报文，接受服务器响应报文
     * @req: 请求参数
     * @signReq: 计算签名时使用的字段顺序
     */
    public static MinShengClearingResponseDTO work(Map<String, String> reqMap, String signSeq) throws CustomException {



        // ** 生成xml报文
        String xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
        xmlStr += "<requestData>";
        String[] nodeArray = signSeq.split(",");
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < nodeArray.length; i++) {
            if (reqMap.containsKey(nodeArray[i].trim())) {
                xmlStr += "<" + nodeArray[i] + ">" + reqMap.get(nodeArray[i]).trim() + "</" + nodeArray[i] + ">";
                sb.append(reqMap.get(nodeArray[i]).trim());
            }
        }

        // 计算签名
        try {
            String signature = sign(getPrk(locPrk), sb.toString());
            xmlStr += "<md5ConSec>" + signature + "</md5ConSec>";
            xmlStr += "</requestData>";
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("计算签名失败");
        }

        // 生产报文密文,并拼接成最终请求报文
        String msgToSend = null;
        try {
            //报文加密
            String encXmlStr = encrypt(xmlStr, aesKey);
            //拼接
            msgToSend = reqMap.get("ChannelID").trim() + encXmlStr;
            logger.info("请求明文:\n" + xmlStr);
            logger.info("请求密文:\n" + encXmlStr);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("生产报文密文,并拼接成最终请求报文失败");
        }


        // 发送POST请求,解密响应报文并验签
        String rspPlain = null;
        try {
            String rsp = sendingRequest(msgToSend, locKeystore, pwdKeystore, locTruststore, pwdTruststore, pwd, rmtUrl);
            System.out.println("服务器响应密文:\n" + rsp);
            // 删除前16位渠道编号
            rsp = rsp.substring(16);
            // 解密
            rspPlain = decrypt(rsp, aesKey);
            System.out.println("服务器响应明文:\n" + rspPlain);

            return generateMinShengClearingResponse(rspPlain);

        } catch (Exception e) {
            e.printStackTrace();
            throw new CustomException("发送POST请求,解密响应报文并验签失败");
        }
    }


    /*
     * 发起代付交易请求，接收同步响应
     * 注意：实际交易结果（代付成功/失败）需以异步响应通知为准。同步响应“成功”只表示代付申请成功！
     * params.put("accNo","") // 收款人卡号
     * params.put("Name","") // 收款人姓名
     * params.put("orderId","")  // 订单号
     * params.put("txnAmt","") // 付款金额（包含手续费，以分为单位）
     */
    public static MinShengClearingResponseDTO DaifuJiaoYi(Map<String, String> params) throws CustomException {
        String channelId = "6600000000000041";    //请使用贵阳民生提供的测试参数进行替换
        String bmMerId = "6600000000000041";    //请使用贵阳民生提供的测试参数进行替换
        String retUrl = "http://clouderp.skystorechain.com:20080/SpringBoot_Dubbo_web/clearing/daiFuAsyncNotificationWeiZhi";

        // ** 组装请求报文
        // 收款账号类型
        params.put("acctType", "1");
        // 时间戳
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置日期格式
        params.put("timeStamp", df.format(new Date()));
        // 渠道号，即s商户号
        params.put("ChannelID", channelId);
        // s商户号
        params.put("bmMerId", bmMerId);
        // 编码方式，固定使用UTF-8
        params.put("charCode", "UTF-8");
        // 协议版本号，固定使用2.0.1
        params.put("Version", "2.0.1");
        // 交易类型，代付取值0250
        params.put("TradeType", "1101");
        // 通知地址
        params.put("retUrl", retUrl);
        // 签名计算顺序
        String signSeq = "charCode,Version,TradeType,ChannelID,bmMerId,timeStamp,acctType,retUrl,accNo,Name,Nbr,certificateCode,orderId,txnAmt";

        return work(params, signSeq);

    }

    /*
     * 代付交易结果查询
     */
    public static MinShengClearingResponseDTO DaifuChaxun(String orderId) throws CustomException {
        Map<String, String> req = new HashMap<String, String>();
        req.put("charCode", "UTF-8");
        req.put("Version", "2.0.1");
        req.put("TradeType", "1103");
        req.put("ChannelID", "6600000000000041");    //请使用贵阳民生提供的测试参数进行替换
        req.put("bmMerId", "6600000000000041");        //请使用贵阳民生提供的测试参数进行替换
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");//设置日期格式
        req.put("timeStamp", df.format(new Date()));
        req.put("orderId", orderId);
        String signSeq = "charCode,Version,TradeType,ChannelID,bmMerId,timeStamp,orderId";
        return work(req, signSeq);
    }

    public static MinShengClearingResponseDTO generateMinShengClearingResponse(String rspPlain) throws Exception {

        // 验签
        String tmp = rspPlain.substring(rspPlain.indexOf("<md5ConSec>"), rspPlain.indexOf("</md5ConSec>") + 12);
        String sigToVerify = tmp.substring(11, tmp.length() - 12);
        System.out.println("服务器签名串:\n" + sigToVerify);
        String strToVerify = rspPlain.substring(rspPlain.indexOf("<responseData>") + 14, rspPlain.indexOf("<md5ConSec>")).replaceAll("<[^>]*>", "");
        System.out.println("待验签字串:\n" + strToVerify);

        Boolean flag = verify(getPuk(locPuk), strToVerify, sigToVerify);

        Document document = DocumentHelper.parseText(rspPlain);
        Element root = document.getRootElement();
        MinShengClearingResponseDTO minShengClearingResponseDTO = new MinShengClearingResponseDTO();
        minShengClearingResponseDTO.setVerify(flag);
        Element TradeType = root.element("TradeType");
        if (TradeType != null) {
            minShengClearingResponseDTO.setTradeType(TradeType.getText());
        }
        Element ChannelID = root.element("ChannelID");
        minShengClearingResponseDTO.setChannelID(ChannelID.getText());

        Element bmMerId = root.element("bmMerId");
        minShengClearingResponseDTO.setBmMerId(bmMerId.getText());

        Element timeStamp = root.element("timeStamp");
        minShengClearingResponseDTO.setTimeStamp(timeStamp.getText());

        Element resultCode = root.element("resultCode");
        if (resultCode != null){
            minShengClearingResponseDTO.setResultCode(resultCode.getText());
        }

        Element accNo = root.element("accNo");
        if (accNo != null) {
            minShengClearingResponseDTO.setAccNo(accNo.getText());
        }
        Element resultDesc = root.element("resultDesc");
        if (resultDesc != null) {
            minShengClearingResponseDTO.setResultDesc(resultDesc.getText());
        }

        Element txnAmt = root.element("txnAmt");
        if (txnAmt != null){
            minShengClearingResponseDTO.setTxnAmt(txnAmt.getText());
        }

        Element orderId = root.element("orderId");
        minShengClearingResponseDTO.setOrderId(orderId.getText());

        /*Element md5ConSec = root.element("md5ConSec");
        minShengClearingResponseDTO.setMd5ConSec(md5ConSec.getText());*/

        return minShengClearingResponseDTO;
    }

    public static void main(String[] as) throws Exception {

       // String str ="uxUsO6Itip7oOQX2V5RJXuwPYqTCaTBXthNpKaJc/lJsWTyMpqku3C18u+KuRmnVfjp7TY5rAajLSD8PyvXBBKZCoX6olmHMacIlSg31K5qTuuIKHA796PVqfAb+VNvmaB6gR1Gjo5qkTAp1M+1tkXGCM3VRdQKBnzwlAZ3vhmDW0j6Szw6kSicc4j5gkX7Njn8OdtiKxUz4G7FtgXHY5r/XHV448wX650KxejgdM2L5c32Q1uo/Ox8TKBKt5op+jEzyOG54MlOOXk0PFoh8pzJbPky/EmxPm/O/EGmKb5fdYuD1G6CkRhUBSBJUErfPfRH4pylpbZZbEUFLBxCxuD3Md2c0ZE1XfsyZDN+zdOrZcCytz2aRVktf/UruJA0kpPoxtnC+AyEVvj0ZiQkRfnob0d8GnfQ6q5IOF1azEP7tme85TCHqjKeHwaxFCCbG6RyWc9WpTobHgtd0RYhNgl27RnMq1MpRGIS7eQ8dDHheVT0tTiq7X9H5WO31k8LRuAKYMtKYmfGOTRDsnx7quZHBbt9lbWuYQxEX2VhtF0Y6fLEi0IW2rvTpZ2RbL8qCIT+2Ik+4FY0fjDgbbb7Rx+UZXSJNCGcczvUQ2xfqhEMn8YLs65BozhVjQdini/1CDj+1BKd26QrNbxqT8cqUHwHN2tJS4NQeTeq7L6BerrJuZEe8c/+0ZUB+mHTnkFIRZSBuTI0lJ436Q+lXPlClujB2FtXJM59pLHnx8B4Hps0MUeFr+cXp7RFfIFMQ+d67irGTNkH3juZx7p86t5eYbOwOKeFHlJmEKR6IhOI44+eGWfMQULOSQYcyN/ktrR4h33z0hO4kBiqGEHDxjeoSCYlzxSyI48DqHzKf0Ae1SJSPyNhX+ehpYUMZ1Fq3wSyIorje7V3ga4A+uORHakxNbaFy5FFi24D3ZnwXhluqX/MqzyOgWuwapPp8LaLXib1s9qB/sDUj6D4pO4Jdcv6icg==";
        //MinShengClearingResponseDTO minShengClearingResponseDTO = generateMinShengClearingResponse(jieMi(str));
        //String msg = minShengClearingResponseDTO.getResultDesc();
        //String utf8 = URLDecoder.decode(msg,"utf-8");
        //System.out.println(utf8);
        //System.out.println(minShengClearingResponseDTO);
        //System.exit(0);
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");//设置日期格式
        String orderId = df.format(new Date()) + "0000";
        System.out.println(orderId);
        logger.info("---------------------------------------- 代付交易请求:");
        Map<String, String> params = new HashMap();
        params.put("accNo", "6216911300811648"); // 收款人卡号
        params.put("Name", "郑川"); // 收款人姓名
        params.put("orderId", orderId);  // 订单号
        params.put("txnAmt", "100"); // 付款金额（包含手续费，以分为单位）
        params.put("certificateCode", ""); // 身份证号

        MinShengClearingResponseDTO ms1 = DaifuJiaoYi(params);
        System.out.println(ms1);
        try {
            Thread.currentThread().sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.info("---------------------------------------- 代付交易结果查询:");
        MinShengClearingResponseDTO ms2 = DaifuChaxun(orderId);
        System.out.println(ms2);
    }

    public static String jieMi(String rsp){
        String rspPlain = null;
        System.out.println("服务器响应密文:\n" + rsp);
        // 删除前16位渠道编号
        //rsp = rsp.substring(16);
        // 解密
        rspPlain = decrypt(rsp, aesKey);
        System.out.println("服务器响应明文:\n" + rspPlain);
        return rspPlain;
    }
}
